#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" ISAPI filter for serving WSGI apps.

Based on http://isapi-wsgi.googlecode.com/svn/trunk/isapi_wsgi.py

When used as a module, other WSGI apps can install themselves in the
same way.  See the install() function below (note the lack of
doctests).
"""
import os
import sys

from cStringIO import StringIO
from wsgiref.handlers import BaseHandler

from isapi import ExtensionError
from isapi.simple import SimpleExtension
from isapi.threaded_extension import ThreadPoolExtension


__trace__ = 0


if __trace__:
    import trace
    import win32traceutil
    tracer = trace.Trace() #ignoredirs=(sys.prefix, sys.exec_prefix))
    def traced(func):
        def deco(*args, **kwds):
            return tracer.runfunc(func, *args, **kwds)
        return deco
else:
    def traced(func):
        return func


required_cgienv_vars = [
    'APPL_MD_PATH',
    'APPL_PHYSICAL_PATH',
    'CONTENT_LENGTH',
    'CONTENT_TYPE',
    'INSTANCE_ID',
    'INSTANCE_META_PATH',
    'PATH_INFO',
    'PATH_TRANSLATED',
    'QUERY_STRING',
    'REQUEST_METHOD',
    'SCRIPT_NAME',
    'SERVER_NAME',
    'SERVER_PORT',
    'SERVER_PROTOCOL',
    'SERVER_SOFTWARE',
    ]


class IsapiInput:
    """

    """
    ## @traced
    def __init__(self, control_block):
        self._in = infile = StringIO()
        self._control_block = control_block
        if control_block.AvailableBytes > 0:
            data = control_block.AvailableData
            excess = control_block.TotalBytes - control_block.AvailableBytes
            if excess > 0:
                extra = control_block.ReadClient(excess)
                data = data + extra
            infile.write(data)
        infile.seek(0)

    ## @traced
    def next(self):
        return self._in.next()

    ## @traced
    def read(self, size=-1):
        return self._in.read(size)

    ## @traced
    def readline(self):
        return self._in.readline()

    ## @traced
    def readlines(self, hint=-1):
        return self._in.readlines()

    ## @traced
    def reset(self):
        self._in.reset()

    ## @traced
    def seek(self, *args, **kwargs):
        self._in.seek(*args, **kwargs)

    ## @traced
    def tell(self):
        return self._in.tell()

    ## @traced
    def __iter__(self):
        return iter(self._in.readlines())


class IsapiOutput:
    """

    """
    ## @traced
    def __init__(self, control_block):
        self.control_block = control_block

    ## @traced
    def write(self, msg):
        self.control_block.WriteClient(msg)

    ## @traced
    def flush(self):
        """ No-op """


class IsapiWsgiHandler(SimpleExtension, BaseHandler):
    """ Python Simple WSGI ISAPI Extension

    The ISAPI extension - handles requests in our virtual dir, and
    sends the response to the client.
    """
    ## @traced
    def __init__(self, control_block):
        self.control_block = control_block
        self.stdin = IsapiInput(control_block)
        self.stdout = IsapiOutput(control_block)
        self.stderr = sys.stderr
        self.base_env = []
        self.headers = None
        self.headers_sent = False
        self.wsgi_multithread = False
        self.wsgi_multiprocess = False

    ## @traced
    def TerminateExtension(self, status):
        """ Called by the ISAPI framework as the extension terminates.

        Overridden from SimpleExtension.
        """

    ## @traced
    def send_preamble(self):
        """ Since ISAPI sends preamble itself, do nothing

        Overridden from BaseHandler.
        """

    ## @traced    
    def send_headers(self):
        """ Transmit headers to the client, via self._write()

        Overridden from BaseHandler.
        """
        self.cleanup_headers()
        self.headers_sent = True
        if not self.origin_server or self.client_is_modern():
            self.control_block.SendResponseHeaders(self.status, str(self.headers), False)

    ## @traced
    def _write(self, data):
        """ Send data to the client.

        Overridden from BaseHandler.
        """
        self.control_block.WriteClient(data)

    ## @traced
    def _flush(self):
        """ Flush recent data.

        Overridden from BaseHandler.
        """

    ## @traced
    def get_stdin(self):
        """ Returns reference to this handler's stdin

        Overridden from BaseHandler.
        """
        return self.stdin

    ## @traced
    def get_stderr(self):
        """ Returns reference to this handler's stderr

        Overridden from BaseHandler.
        """
        return self.stderr

    ## @traced
    def add_cgi_vars(self):
        """ Inserts CGI variables into self.environ

        Overridden from BaseHandler.
        """
        # get standard windows os environment
        environ = os.environ.copy()
        # set standard CGI variables
        for name in required_cgienv_vars:
            try:
                environ[name] = self.control_block.GetServerVariable(name)
            except (Exception, ):
                msg = 'Missing CGI environment variable %s' % name
                raise AssertionError(msg)

        # Original comment:
        ## Due to an IIS bug ISAPI returns incorrect PATH_INFO and SCRIPT_NAME 
        ## variables. Both variables are the extension name and the rest of 
        ## the path upto the ?
        ## Code below corrects the variables.

        # Original code:
        ##pathinfo = environ['PATH_INFO']
        ##scriptname = fix_script_name(environ['SCRIPT_NAME'])
        ##environ['SCRIPT_NAME'] = scriptname
        ##environ['PATH_INFO'] = fix_path_info(scriptname, pathinfo)

        # Replacement comment:
        ## We choose to not monkey with the SCRIPT_NAME and PATH_INFO.  IIS
        ## gets it wrong, for sure, but the client can better define how to
        ## handle that.

        all_http = self.control_block.GetServerVariable('ALL_HTTP')
        http_cgienv_vars = all_http.split('\n')
        for name in http_cgienv_vars:
            pair = name.split(':', 1)
            try:
                environ[pair[0]] = pair[1]
            except:
                pass

        try:
            remote_user = self.control_block.GetServerVariable('REMOTE_USER')
            environ['REMOTE_USER'] = remote_user
        except (Exception, ):
            pass
        self.environ.update(environ)


class WsgiThreadPoolHandler(ThreadPoolExtension):
    """ Python Thread Pool WSGI ISAPI Extension

    """
    ## @traced
    def __init__(self, app):
        ThreadPoolExtension.__init__(self)
        self.app = app

    ## @traced
    def Dispatch(self, control_block):
        application = self.app
        handler = IsapiWsgiHandler(control_block)
        try:
            if application is not None:
                handler.run(application)
            else:
                handler.run(isapi_error)
        except (ExtensionError, ):
            ## error normally happens when client disconnects before
            ## extension i/o completed
            pass
        except (Exception, ):
            ## TODO:Other exceptions should generate a nice page
            pass
        control_block.DoneWithSession()


## @traced
def error404_app(environ, start_response):
    """ Send an error page to the client.

    """
    start_response('404 Not Found', [('Content-type', 'text/plain')])
    return ['404 Not Found']


def install():
    """ Install a virtual directory.

    """    
    from optparse import OptionParser
    from isapi.install import ISAPIParameters, ScriptMapParams, \
         VirtualDirParameters, HandleCommandLine

    def pre_install(params, options):
        """ Pre-install hook for our virtual directory.

        """
        if not options.server:
            print 'Must supply --server argument.'
            #sys.exit(1)
            return
        if not options.name:
            print 'Must supply --name argument.'
            #sys.exit(1)
            return
        params.Description = options.description
        params.Name = options.name

    params = ISAPIParameters()
    params.VirtualDirs = [
            VirtualDirParameters(
                Name='',
                Description='',
                ScriptMaps=[ScriptMapParams(Extension='*', Flags=0)],
                ScriptMapUpdate='replace',
                PreInstall=pre_install
            ),
    ]

    parser = OptionParser('')
    parser.add_option('-d',
                      '--description',
                      action='store',
                      default='WSGI ISAPI Extension Demo',
                      help='Description to use for the virtual directory')
    parser.add_option('-n',
                      '--name',
                      action='store',
                      default='',
                      help='Name of virtual directory')
    HandleCommandLine(params, opt_parser=parser)
